package cn.iocoder.yudao.module.lowcode.core.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Groovy 脚本安全管理器
 * 
 * 提供脚本安全检查和风险评估功能
 *
 * @author 芋道源码
 */
@Component
@Slf4j
public class GroovyScriptSecurityManager {

    /**
     * 禁止的包名和类名
     */
    private static final Set<String> FORBIDDEN_PACKAGES = new HashSet<>(Arrays.asList(
        "java.lang.Runtime",
        "java.lang.Process",
        "java.lang.ProcessBuilder",
        "java.io.File",
        "java.io.FileInputStream",
        "java.io.FileOutputStream",
        "java.nio.file",
        "java.lang.System",
        "java.lang.ClassLoader",
        "java.lang.reflect",
        "javax.script.ScriptEngineManager",
        "groovy.util.Eval"
    ));

    /**
     * 禁止的方法调用
     */
    private static final Set<String> FORBIDDEN_METHODS = new HashSet<>(Arrays.asList(
        "exec",
        "execute",
        "getRuntime",
        "exit",
        "halt",
        "load",
        "loadLibrary",
        "setSecurityManager",
        "getProperty",
        "setProperty",
        "getenv"
    ));

    /**
     * 危险关键字模式
     */
    private static final Pattern[] DANGEROUS_PATTERNS = {
        Pattern.compile("Runtime\\.getRuntime\\(\\)", Pattern.CASE_INSENSITIVE),
        Pattern.compile("ProcessBuilder", Pattern.CASE_INSENSITIVE),
        Pattern.compile("System\\.exit", Pattern.CASE_INSENSITIVE),
        Pattern.compile("System\\.getProperty", Pattern.CASE_INSENSITIVE),
        Pattern.compile("Class\\.forName", Pattern.CASE_INSENSITIVE),
        Pattern.compile("Thread\\.sleep", Pattern.CASE_INSENSITIVE),
        Pattern.compile("\\.exec\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("new\\s+File\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("FileInputStream", Pattern.CASE_INSENSITIVE),
        Pattern.compile("FileOutputStream", Pattern.CASE_INSENSITIVE),
        Pattern.compile("while\\s*\\(\\s*true\\s*\\)", Pattern.CASE_INSENSITIVE),
        Pattern.compile("for\\s*\\(\\s*;;\\s*\\)", Pattern.CASE_INSENSITIVE),
        // 新增更多安全检查
        Pattern.compile("URLClassLoader", Pattern.CASE_INSENSITIVE),
        Pattern.compile("ScriptEngine", Pattern.CASE_INSENSITIVE),
        Pattern.compile("SecurityManager", Pattern.CASE_INSENSITIVE),
        Pattern.compile("AccessController", Pattern.CASE_INSENSITIVE),
        Pattern.compile("PrivilegedAction", Pattern.CASE_INSENSITIVE),
        Pattern.compile("System\\.setProperty", Pattern.CASE_INSENSITIVE),
        Pattern.compile("Runtime\\.addShutdownHook", Pattern.CASE_INSENSITIVE),
        Pattern.compile("Thread\\.currentThread\\(\\)\\.setContextClassLoader", Pattern.CASE_INSENSITIVE),
        // 防止网络访问
        Pattern.compile("new\\s+Socket\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("new\\s+URL\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("HttpURLConnection", Pattern.CASE_INSENSITIVE),
        // 防止反射滥用
        Pattern.compile("\\.getMethod\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("\\.getDeclaredMethod\\s*\\(", Pattern.CASE_INSENSITIVE),
        Pattern.compile("\\.invoke\\s*\\(", Pattern.CASE_INSENSITIVE)
    };

    /**
     * 检查脚本安全性
     */
    public ScriptSecurityResult checkScriptSecurity(String scriptContent) {
        ScriptSecurityResult result = new ScriptSecurityResult();
        
        // 检查禁止的包和类
        for (String forbidden : FORBIDDEN_PACKAGES) {
            if (scriptContent.contains(forbidden)) {
                result.addViolation(SecurityViolationType.FORBIDDEN_PACKAGE, 
                    "检测到禁止使用的包或类: " + forbidden);
            }
        }
        
        // 检查禁止的方法
        for (String method : FORBIDDEN_METHODS) {
            if (scriptContent.contains(method + "(")) {
                result.addViolation(SecurityViolationType.FORBIDDEN_METHOD, 
                    "检测到禁止使用的方法: " + method);
            }
        }
        
        // 检查危险模式
        for (Pattern pattern : DANGEROUS_PATTERNS) {
            if (pattern.matcher(scriptContent).find()) {
                result.addViolation(SecurityViolationType.DANGEROUS_PATTERN, 
                    "检测到危险代码模式: " + pattern.pattern());
            }
        }
        
        // 检查脚本长度
        if (scriptContent.length() > 100000) {
            result.addViolation(SecurityViolationType.SCRIPT_TOO_LARGE, 
                "脚本过大，可能存在风险");
        }
        
        // 检查嵌套深度
        int maxNesting = calculateMaxNesting(scriptContent);
        if (maxNesting > 10) {
            result.addViolation(SecurityViolationType.EXCESSIVE_NESTING, 
                "脚本嵌套深度过深: " + maxNesting);
        }
        
        return result;
    }

    /**
     * 计算最大嵌套深度
     */
    private int calculateMaxNesting(String script) {
        int maxDepth = 0;
        int currentDepth = 0;
        
        for (char c : script.toCharArray()) {
            if (c == '{') {
                currentDepth++;
                maxDepth = Math.max(maxDepth, currentDepth);
            } else if (c == '}') {
                currentDepth--;
            }
        }
        
        return maxDepth;
    }

    /**
     * 安全检查结果
     */
    public static class ScriptSecurityResult {
        private boolean safe = true;
        private final Set<SecurityViolation> violations = new HashSet<>();

        public void addViolation(SecurityViolationType type, String message) {
            this.safe = false;
            this.violations.add(new SecurityViolation(type, message));
        }

        public boolean isSafe() {
            return safe;
        }

        public Set<SecurityViolation> getViolations() {
            return violations;
        }

        public String getViolationMessages() {
            return violations.stream()
                    .map(SecurityViolation::getMessage)
                    .reduce((a, b) -> a + "; " + b)
                    .orElse("");
        }
    }

    /**
     * 安全违规类型
     */
    public enum SecurityViolationType {
        FORBIDDEN_PACKAGE("禁止的包"),
        FORBIDDEN_METHOD("禁止的方法"),
        DANGEROUS_PATTERN("危险模式"),
        SCRIPT_TOO_LARGE("脚本过大"),
        EXCESSIVE_NESTING("过度嵌套");

        private final String description;

        SecurityViolationType(String description) {
            this.description = description;
        }

        public String getDescription() {
            return description;
        }
    }

    /**
     * 安全违规记录
     */
    public static class SecurityViolation {
        private final SecurityViolationType type;
        private final String message;

        public SecurityViolation(SecurityViolationType type, String message) {
            this.type = type;
            this.message = message;
        }

        public SecurityViolationType getType() {
            return type;
        }

        public String getMessage() {
            return message;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (!(obj instanceof SecurityViolation)) return false;
            SecurityViolation that = (SecurityViolation) obj;
            return type == that.type && message.equals(that.message);
        }

        @Override
        public int hashCode() {
            return type.hashCode() * 31 + message.hashCode();
        }
    }
}